//
// Copyright (c) 2009 All Right Reserved
//
// vl
//
// 2009-01-01
// Contains ...
using System;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Text;
using System.Xml.Linq;
using System.Xml.Serialization;
using LargoCommon.Abstract;
using LargoCommon.Interfaces;
namespace LargoCommon.Music
{
/// Melodic tone.
/// Tone is defined as pitch placed in given bit Range with a given loudness.
/// (it would possibly incorporate melodic ornaments?).
/// It is planned to keep some properties of harmonic clusters
/// (e.g. harmonic cover, potential or doubling).
[Serializable]
[XmlRoot]
public sealed class MusicalTone : MusicalStrike, IComparable
{
#region Fields
///
/// Musical pitch.
///
private MusicalPitch pitch;
///
/// Sound weight.
///
private float weight;
#endregion
#region Constructors
/// Initializes a new instance of the MusicalTone class. Serializable.
public MusicalTone() {
this.ToneType = MusicalToneType.Melodic;
this.weight = -1;
}
/// Initializes a new instance of the MusicalTone class.
/// Rhythmical range of bits.
/// Number of musical bar.
public MusicalTone(BitRange givenBitRange, int barNumber)
: base(MusicalToneType.Melodic, givenBitRange, 0, barNumber) {
this.weight = -1;
}
/// Initializes a new instance of the MusicalTone class.
/// Musical pitch.
/// Rhythmical range of bits.
/// Musical loudness.
/// Number of musical bar.
public MusicalTone(MusicalPitch givenPitch, BitRange givenBitRange, MusicalLoudness loudness, int barNumber)
: base(MusicalToneType.Melodic, givenBitRange, loudness, barNumber) {
this.weight = -1;
this.Pitch = givenPitch;
//// if (this.IsEmpty) { return; }
}
///
/// Initializes a new instance of the MusicalTone class.
///
/// Musical pitch.
/// Rhythmic Order.
/// Musical duration.
/// Musical loudness.
/// Number of musical bar.
public MusicalTone(MusicalPitch givenPitch, byte givenRhythmicOrder, byte givenDuration, MusicalLoudness loudness, int barNumber)
: base(MusicalToneType.Melodic, givenRhythmicOrder, givenDuration, loudness, barNumber) {
this.weight = -1;
this.Pitch = givenPitch;
//// if (this.IsEmpty) { return; }
}
/// Initializes a new instance of the MusicalTone class.
/// Type of tone.
/// Rhythmical range of bits.
/// Musical loudness.
/// Number of musical bar.
public MusicalTone(MusicalToneType toneType, BitRange givenBitRange, MusicalLoudness loudness, int barNumber)
: base(toneType, givenBitRange, loudness, barNumber) {
this.weight = -1;
}
/// Initializes a new instance of the MusicalTone class.
/// Type of tone.
/// Rhythmical order.
/// Musical duration.
/// Musical loudness.
/// Number of musical bar.
public MusicalTone(MusicalToneType toneType, byte givenRhythmicOrder, byte givenDuration, MusicalLoudness loudness, int barNumber)
: base(toneType, givenRhythmicOrder, givenDuration, loudness, barNumber) {
this.weight = -1;
}
///
/// Initializes a new instance of the class.
///
/// The xml element.
/// The rhythmic order.
public MusicalTone(XElement xelement, byte rorder) {
//// if (xelement == null) { return; }
this.BarNumber = XmlSupport.ReadIntegerAttribute(xelement.Attribute("Bar"));
//// this.bitRange = new BitRange();
//// this.bitRange.SetXElement(element);
this.BitFrom = XmlSupport.ReadByteAttribute(xelement.Attribute("Start"));
this.Duration = XmlSupport.ReadByteAttribute(xelement.Attribute("Length"));
this.BitRange = new BitRange(rorder, this.BitFrom, (byte)this.Duration);
//// string s = LibSupport.ReadStringAttribute(xelement.Attribute("ToneType"));
//// this.ToneType = string.IsNullOrEmpty(s) ? MusicalToneType.Empty : (MusicalToneType)Enum.Parse(typeof(MusicalToneType), s);
this.ToneType = MusicalToneType.Melodic;
this.Loudness = DataEnums.ReadAttributeMusicalLoudness(xelement.Attribute("Loudness"));
//// 2018/10 !?!?!? commented - instrument directed by status
//// 2018/12 back to tone instruments
var attrInstr = xelement.Attribute("Instrument");
if (attrInstr != null) {
this.InstrumentNumber = XmlSupport.ReadByteAttribute(attrInstr);
//// see lastInstrument ...
}
else {
this.InstrumentNumber = (int)MidiMelodicInstrument.None;
}
var harmonicSystem = HarmonicSystem.GetHarmonicSystem(DefaultValue.HarmonicOrder);
//// if (harmonicSystem == null) { return; }
var xpitch = xelement.Attribute("Pitch");
if (xpitch == null) {
return;
}
this.pitch = new MusicalPitch(harmonicSystem);
this.pitch.SetXAttribute(xpitch);
}
#endregion
#region Properties - Xml
/// Gets Xml representation.
/// Property description.
public override XElement GetXElement {
get {
XElement xe;
if (this.pitch != null) {
xe = new XElement(
"Tone", //// xmlMelTone.Add(new XAttribute("ToneType", this.ToneType));
new XAttribute("Bar", this.BarNumber),
new XAttribute("Start", this.BitFrom), //// this.BitRange.GetXElement
new XAttribute("Length", this.Duration),
new XAttribute("Note", this.Pitch.ToString()), //// this.Pitch.GetXElement
this.pitch.GetXAttribute);
//// 2019/01 if (this.Loudness != MusicalLoudness.MeanLoudness) {
xe.Add(new XAttribute("Loudness", this.Loudness.ToString()));
//// }
}
else {
xe = new XElement("Pause", new XAttribute("Length", this.Duration));
}
return xe;
}
}
#endregion
#region Tone Properties
///
/// Gets or sets the pause - pause following the note (e.g. because of note shortening)
/// Duration of the pause is calculated separately.
///
///
/// The pause.
///
public MusicalPause Pause { get; set; }
/// Gets or sets index to harmonic modality.
/// Property description.
public short ModalityIndex { get; set; }
/// Gets or sets index to harmonic structure.
/// Property description.
public byte HarmonicIndex { get; set; }
/// Gets or sets index for better identification of intervals (see HarmonicStateReal).
/// Property description.
public byte ToneIndex { get; set; }
/// Gets musical pitch.
/// Property description.
public MusicalPitch Pitch {
get {
//// Contract.Requires(this.pitch != null);
Contract.Ensures(Contract.Result() != null);
//// Time optimization - critical time of comparison !?
//// if (this.pitch == null) { throw new InvalidOperationException("Pitch is null."); }
return this.pitch;
}
private set => this.pitch = value?.Clone() as MusicalPitch;
}
/// Gets weight of the tone.
/// Property description.
public float Weight {
get {
if (this.weight >= 0) {
return this.weight;
}
var altitude = this.Pitch.OctaveAltitude(0f); // -4.0f
var w = altitude >= DefaultValue.AfterZero && altitude <= DefaultValue.LargeNumber ? (short)this.Loudness / altitude : 0;
this.weight = w;
return this.weight;
}
}
/// Gets a value indicating whether Is empty tone.
/// Property description.
/// Returns value.
public override bool IsEmpty => this.pitch == null;
/// Gets a value indicating whether is melodic tone.
/// Property description.
/// Returns value.
public bool IsTrueTone => (this.ToneType == MusicalToneType.Melodic) && (this.pitch != null) && (this.Loudness > 0);
///
/// Gets the melodic identifier.
///
///
/// The melodic identifier.
///
public override string MelodicIdentifier => $"{this.Pitch}#{this.BitFrom}/{this.Duration}";
///
/// Gets the rhythmic identifier.
///
///
/// The rhythmic identifier.
///
public override string RhythmicIdentifier => $"{this.BitFrom}/{this.Duration}";
#endregion
#region Note Properties
/// Gets or sets The MIDI note to modify (0x0 to 0x7F).
/// General musical property.
public override byte NoteNumber {
get {
if (this.Pitch == null) {
return 0;
}
return this.Pitch.MidiKeyNumber; //// (this.Pitch != null) ? this.Pitch.MidiKeyNumber : (byte)0;
}
}
/// Gets or sets The MIDI note to modify (0x0 to 0x7F).
/// General musical property.
public override string Note => MusicalProperties.GetNoteNameAndOctave(this.NoteNumber, DefaultValue.HarmonicOrder);
///
/// Gets the note letter.
///
/// Property description.
public string NoteLetter => MusicalProperties.GetSingleNoteName(this.NoteNumber);
///
/// Gets the note alter.
///
/// Property description.
public short NoteAlter => MusicalProperties.GetAlterSign(this.NoteNumber);
#endregion
#region Static Factory Methods
///
/// Creates MusicalTone.
///
/// Harmonic system.
/// The bit range.
/// Midi note.
/// Bar number.
/// Tone loudness.
///
/// Returns value.
///
public static IMusicalTone CreateMelodicTone(
HarmonicSystem harmonicSystem,
BitRange bitRange,
byte midiNote,
int barNumber,
MusicalLoudness toneLoudness) {
Contract.Requires(harmonicSystem != null);
//// if (harmonicSystem == null) { return; }
IMusicalTone musTone;
if (midiNote > 0 && toneLoudness != MusicalLoudness.None) {
//// musPitch = new MusicalPitch(harmonicSystem, midiNote);
var musPitch = harmonicSystem.GetPitch(midiNote);
var loudness = toneLoudness;
//// loudness = 5;
musTone = new MusicalTone(musPitch, bitRange, loudness, barNumber);
}
else {
musTone = new MusicalPause(bitRange.Order, bitRange.Length, barNumber);
}
return musTone;
}
#endregion
#region Static operators
//// TICS rule 7@526: Reference types should not override the equality operator (==)
//// public static bool operator ==(MusicalTone tone1, MusicalTone tone2) { return object.Equals(tone1, tone2); }
//// public static bool operator !=(MusicalTone tone1, MusicalTone tone2) { return !object.Equals(tone1, tone2); }
//// but TICS rule 7@530: Class implements interface 'IComparable' but does not implement '==' and '!='.
///
/// Implements the operator <.
///
/// The object1.
/// The object2.
///
/// Returns value.
///
public static bool operator <(MusicalTone object1, MusicalTone object2) {
if (object1 != null && object2 != null) {
return object1.Pitch < object2.Pitch;
}
return false;
}
///
/// Implements the operator >.
///
/// The object1.
/// The object2.
///
/// Returns value.
///
public static bool operator >(MusicalTone object1, MusicalTone object2) {
if (object1 != null && object2 != null) {
return object1.Pitch > object2.Pitch;
}
return false;
}
///
/// Implements the operator <=.
///
/// The object1.
/// The object2.
///
/// Returns value.
///
public static bool operator <=(MusicalTone object1, MusicalTone object2) {
if (object1 != null && object2 != null) {
return object1.Pitch <= object2.Pitch;
}
return false;
}
///
/// Implements the operator >=.
///
/// The object1.
/// The object2.
///
/// Returns value.
///
public static bool operator >=(MusicalTone object1, MusicalTone object2) {
if (object1 != null && object2 != null) {
return object1.Pitch >= object2.Pitch;
}
return false;
}
#endregion
#region Comparison
/// Compare tones.
/// Object to be compared.
/// Returns value.
public override int CompareTo(object value) {
var mt = value as MusicalTone;
if (mt?.pitch == null || this.pitch == null) {
return 0;
}
return this.pitch.CompareTo(mt.Pitch);
//// This kills the DataGrid
//// throw new ArgumentException("Object is not a MusicalTone");
}
/// Test of equality.
/// Object to be compared.
/// Returns value.
public override bool Equals(object obj) {
//// check null (this pointer is never null in C# methods)
if (object.ReferenceEquals(obj, null)) {
return false;
}
if (object.ReferenceEquals(this, obj)) {
return true;
}
if (this.GetType() != obj.GetType()) {
return false;
}
return this.CompareTo(obj) == 0;
}
/// Support of comparison.
/// Returns value.
public override int GetHashCode() {
return this.Pitch == null ? 0 : this.Pitch.GetHashCode();
}
#endregion
#region Public methods
///
/// Makes a deep copy of the MusicalStrike object.
///
///
/// Returns object.
///
public override object Clone() {
return this.CloneTone();
}
/// Makes a deep copy of the MusicalTone object.
/// Returns object.
public override object CloneTone() {
var mt = new MusicalTone(this.Pitch, this.BitRange, this.Loudness, this.BarNumber) { //// (this.BarNumber)
ModalityIndex = this.ModalityIndex,
HarmonicIndex = this.HarmonicIndex,
OrdinalIndex = this.OrdinalIndex,
IsFromPreviousBar = this.IsFromPreviousBar, //// 2013/03
IsGoingToNextBar = this.IsGoingToNextBar,
InstrumentNumber = this.InstrumentNumber,
//// Channel = this.Channel,
Staff = this.Staff,
Voice = this.Voice
};
return mt;
}
/// Accepts properties of the given tone.
/// Melodic tone.
public void SetMelTone(MusicalTone givenTone) {
Contract.Requires(givenTone != null);
//// if (givenTone == null) { return false; }
this.SetMusicalTone(givenTone);
this.Pitch = givenTone.Pitch;
this.ModalityIndex = givenTone.ModalityIndex;
this.HarmonicIndex = givenTone.HarmonicIndex;
this.OrdinalIndex = givenTone.OrdinalIndex;
this.IsFromPreviousBar = givenTone.IsFromPreviousBar; //// 2013/03
this.IsGoingToNextBar = givenTone.IsGoingToNextBar;
this.InstrumentNumber = givenTone.InstrumentNumber;
//// this.Channel = givenTone.Channel;
this.Staff = givenTone.Staff;
this.Voice = givenTone.Voice;
}
///
/// Sets the pitch.
///
/// The given pitch.
public void SetPitch(MusicalPitch givenPitch) {
this.Pitch = givenPitch;
}
#endregion
#region String representation
/// String representation of the object.
/// Returns value.
public override string ToShortString() {
var s = new StringBuilder();
if (!this.IsEmpty && this.Loudness > 0) {
s.AppendFormat(CultureInfo.CurrentCulture, "{0,3}", this.Pitch);
}
else {
s.Append(MusicalStrike.CPause);
}
return s.ToString();
}
/// String representation of the object.
/// Returns value.
public override string ToString() {
var s = new StringBuilder();
switch (this.ToneType) {
case MusicalToneType.Rhythmic:
s.AppendFormat(CultureInfo.CurrentCulture, "{0,3}", this.Loudness > 0 ? MusicalStrike.CBeat : MusicalStrike.CPause);
break;
case MusicalToneType.Melodic:
s.AppendFormat(CultureInfo.CurrentCulture, "{0,3}", !this.IsEmpty && this.Loudness > 0 ? this.Pitch.ToString() : MusicalStrike.CPause);
break;
case MusicalToneType.Empty:
break;
}
//// for (byte level = 2; level <= this.Duration; level++) { s.AppendFormat(CultureInfo.CurrentCulture, "{0,3}", MusicalStrike.CRepeat); }
//// s.Append("(" + Duration.ToString(System.Globalization.CultureInfo.CurrentCulture.NumberFormat));
//// s.Append("<" + Loudness.ToString(System.Globalization.CultureInfo.CurrentCulture.NumberFormat) + ">");
s.AppendFormat(" <{0,3}>", this.Duration);
//// s.Append(base.ToString());
return s.ToString();
}
#endregion
}
}